// 基于 SnowLang Project (https://gitee.com/jcnc-org/snow) 的衍生作品
// 原始版权归属: Copyright © 2025 Ke Xu (Luke)
// 本实现版权归属: Copyright © 2025 Sany
// 遵循 Apache License 2.0 授权
package module

import (
	"fmt"

	"gitee.com/xntk2381/cc/vm/utils"
)

const MAX_STACK_DEPTH = 1024 // 栈溢出保护
/**
 * CallStack manages the stack frames, maintaining the function call hierarchy.
 */

type CallStack struct {
	stack []*StackFrame
}

/**
 * Default constructor for creating an instance of CallStack.
 * This constructor is empty as no specific initialization is required.
 */
func NewCallStack() *CallStack {
	return &CallStack{
		stack: make([]*StackFrame, 0),
	}
}

/**
 * Pushes a new stack frame onto the call stack.
 *
 * @param frame The stack frame to be pushed.
 * @throws StackOverflowError If the stack exceeds the maximum allowed depth.
 */

func (c *CallStack) PushFrame(frame *StackFrame) error {
	if len(c.stack) >= MAX_STACK_DEPTH {
		return fmt.Errorf("call stack overflow. maximum depth: %d", MAX_STACK_DEPTH)
	}
	c.stack = append(c.stack, frame)
	return nil
}

/**
 * Pops the current stack frame from the call stack.
 *
 * @return The popped stack frame.
 */

func (c *CallStack) PopFrame() (*StackFrame, error) {
	if len(c.stack) == 0 {
		return nil, fmt.Errorf("call stack is empty, cannot pop")
	}
	frame := c.stack[len(c.stack)-1]
	c.stack = c.stack[:len(c.stack)-1]
	return frame, nil
}

/**
 * Retrieves the current (top) stack frame without removing it.
 *
 * @return The top stack frame.
 */

func (c *CallStack) PeekFrame() (*StackFrame, error) {
	if len(c.stack) == 0 {
		return nil, fmt.Errorf("call stack is empty, cannot peek")
	}
	return c.stack[len(c.stack)-1], nil
}

/**
 * Takes a snapshot of the current call stack.
 * Useful for debugging during exceptions.
 *
 * @return A string representing the call stack snapshot.
 */

func (c *CallStack) TakeSnapshot() string {
	snapshot := "--- Call Stack Snapshot ---\n"
	for _, frame := range c.stack {
		methodCtx := frame.GetMethodContext()
		snapshot += fmt.Sprintf("Method: %s, Return Address: %d, Locals: %v\n",
			methodCtx.MethodName(), frame.GetReturnAddress(), frame.GetLocalVariableStore().GetLocalVariables())
	}
	return snapshot
}

/**
 * Checks if the call stack is empty.
 *
 * @return true if empty, false otherwise.
 */
func (c *CallStack) IsEmpty() bool {
	return len(c.stack) == 0
}

/**
 * Prints the current state of the call stack.
 */
func (c *CallStack) PrintCallStack() {
	utils.LogInfo("--- Call Stack State ---", "\n")
	for _, frame := range c.stack {
		frame.PrintFrame()
	}
}
